package de.fhaugsburg.games.boardgames.scrabble;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;

import de.fhaugsburg.games.boardgames.GridPoint;
import de.fhaugsburg.games.boardgames.ManhattanDirection;
import de.fhaugsburg.games.boardgames.exceptions.IllegalMoveException;
import de.fhaugsburg.games.boardgames.scrabble.scrabbleboard.LetterPiece;
import de.fhaugsburg.games.boardgames.scrabble.scrabbleboard.ScrabbleBoard;

/**
 * Helperclass for organizing words put on the board by players thru engine
 *
 */
public class WordManager {

	private ScrabbleBoard board;
	private SortedSet<GridPoint> letters;
	
	
	/**
	 * constructor
	 * @param board
	 */
	public WordManager(ScrabbleBoard board) {
		this.board = board;
		letters = new TreeSet<GridPoint>();
	}
	

	
	/**
	 * a new piece is added
	 * @param piece
	 * @param point
	 * @throws IllegalMoveException 
	 */
	public void addPiece(LetterPiece piece, GridPoint point) throws IllegalMoveException{
		if (board.isEmpty(point)){
			if (point.isManhattanColinearWith(letters)){
				board.putPiece(piece, point);
				letters.add(point);
			}else{
				throw new IllegalMoveException("letters must be placed in a row, either horitzontally or vertically");
			}
		}else{
			//Exceptions are thrown by the board class
			throw new IllegalMoveException("can only place this letter on empty squares");
		}
	}


	/**
	 * removes letter at GridPoint
	 * @param point
	 * @throws IllegalMoveException 
	 */
	public LetterPiece removePiece(GridPoint point) throws IllegalMoveException{
		if (letters.contains(point)){
			letters.remove(point);
			return board.removePiece(point);
		}else{
			throw new IllegalMoveException("unable to remove letter at this position");
		}
	}


	
	
	/**
	 * is given GridPoint already there?
	 * @param point
	 */
	public boolean contains(GridPoint point){
		return letters.contains(point);
	}
	
	
	
	/**
	 * checks if line contains whitespace
	 * @return
	 */
	public boolean containsWhiteSpace(){
		//only one letter?
		if (letters.size() == 1) return false;
		//more? we need the direction
		ManhattanDirection direction = ManhattanDirection.calcDir(letters.first(), letters.last());
		Iterator<GridPoint> it = board.iterator(letters.first(), direction.gridPoint, true);
		GridPoint walkPoint = letters.first();
		while(it.hasNext() && walkPoint.compareTo(letters.last()) < 0){
			if(board.getPiece(walkPoint) == null){
				return true;
			}
			walkPoint = it.next();
		}
		return false;
	}
	
	
	
	
	/**
	 * checks, if our letters are connected to others
	 * @return
	 */
	public boolean isConnectedToOtherWords(){
		//only one letter? we can't calculate direction of this -> check all direction
		if (letters.size() == 1){
			for (ManhattanDirection direction : ManhattanDirection.values()){
				TreeSet<GridPoint> neighborWord = collectWord(letters.first(), direction);
				if(neighborWord != null && neighborWord.size() > 1) return true;
			}
		//more than one letter
		}else{
			ManhattanDirection currentDirection = ManhattanDirection.calcDir(letters.first(), letters.last());
			//letters connect at beginning/end of the word?
			if(collectWord(letters.first(), currentDirection).size() > letters.size()) return true;
			//letters connected on the sides?
			for (GridPoint p: letters){
				TreeSet<GridPoint> neighborWord = collectWord(p, currentDirection.orthogonalDir());
				if (neighborWord != null && neighborWord.size() > 1) return true;
			}
		}
		//not connected at all?
		return false;
	}
	
	
	
	/**
	 * reads the letters and converts them to a string
	 * @param letterSet
	 * @return
	 */
	public String gridPointSetToString(Set<GridPoint> letterSet){
		StringBuilder word = new StringBuilder();
		for (GridPoint p: letterSet){
			word.append(board.getPiece(p).toString());
		}
		return word.toString();
	}
	
	
	
	
	
	/**
	 * get the last put words
	 * why is this a list, instead of an ordinary String??
	 * @return
	 */
	public List<String> getProducedWords(){
		List<String> wordlist = new ArrayList<String>();
		List<TreeSet<GridPoint>> collectedWords = collectProducedWords();
		for (TreeSet<GridPoint> letterSet : collectedWords){
			wordlist.add(gridPointSetToString(letterSet));
		}
		return wordlist;
	}
	
	
	
	
	/**
	 * collect all (whole) words directly connected to our recently dropped letters
	 * @return
	 */
	public List<TreeSet<GridPoint>> collectProducedWords(){
		ArrayList<TreeSet<GridPoint>> wordList = new ArrayList<TreeSet<GridPoint>>();
		TreeSet<GridPoint> collectedWord;
		
		if (letters.size() == 1){
			ManhattanDirection[] horzVert = {ManhattanDirection.RIGHT, ManhattanDirection.DOWN}; 
			for (ManhattanDirection direction : horzVert){
				collectedWord = collectWord(letters.first(), direction);
				if (collectedWord != null && collectedWord.size() > 1) wordList.add(collectedWord);
			}
			if (wordList.size() == 0) wordList.add(new TreeSet<GridPoint>(letters));
			}else{
			//collect the word we are extending in a line, if there is one
			ManhattanDirection currentDirection = ManhattanDirection.calcDir(letters.first(), letters.last());
			wordList.add(collectWord(letters.first(), currentDirection));
			//walk all dropped letters
			for (GridPoint p: letters){
				collectedWord = collectWord(p, currentDirection.orthogonalDir());
				//add to list if this is a word
				if (collectedWord != null && collectedWord.size() > 1) wordList.add(collectedWord);
			}
		}
		return wordList;
	}
	
	
	
	/**
	 * travels pieces in both directions in a line to collect a whole word
	 * @param point
	 * @param direction
	 * @return
	 */
	private TreeSet<GridPoint> collectWord(GridPoint point, ManhattanDirection direction){
		TreeSet<GridPoint> oneWord = new TreeSet<GridPoint>();
		//walk one direction
		GridPoint currentPoint = point;
		GridPoint walkDirection = direction.gridPoint;
		walkOnTheBoard(oneWord, currentPoint, walkDirection);
		//walk the other direction
		walkDirection = direction.gridPoint.neg();
		currentPoint = point.plus(walkDirection);
		walkOnTheBoard(oneWord, currentPoint, walkDirection);
		//one letter is the main word itself
		if (oneWord.size() > 0){
			return oneWord;
		}else{
			return null;
		}
	}
	
	
	
	
	/**
	 * just a little helper
	 * walks the board in one direction, gather letter positions for a word
	 * @param oneWord collects the found letter positions
	 * @param currentPoint for the start
	 * @param walkDirection
	 */
	private void walkOnTheBoard (TreeSet<GridPoint> oneWord, GridPoint currentPoint, GridPoint walkDirection){
		while (board.isOnTheBoard(currentPoint) && board.getPiece(currentPoint) != null) {
			oneWord.add(currentPoint);
			currentPoint = currentPoint.plus(walkDirection);
		}
	}
	
	
	
	
	
	
	
	
	/**
	 * commits the given letters, fixes them on the board, resets on commit
	 * @param first
	 */
	public void commitLetterSequence(boolean first){
		for (GridPoint p: letters){
			board.getDetails(p).setPieceFixed(board.getPiece(p));
		}
	}
	
	
	/**
	 * resets for the next player
	 */
	public void reset() {
		letters.removeAll(letters);
	}
	
	
	
	
	/**
	 * undo everything	
	 * @return
	 */
	public List<LetterPiece> removePreliminaryPieces(){
		List<LetterPiece> removeList = new ArrayList<LetterPiece>();
		for (GridPoint p: letters){
			removeList.add(board.removePiece(p));
		}
		reset();
		return removeList;
	}
	
	
	
	/**
	 * what is this for?
	 * @return
	 */
	public int changePreliminaryStatus(){
		//TODO
		throw new UnsupportedOperationException();
	}
	
	
	
	/**
	 * get current dropped word
	 * @return
	 */
	public String getCurrentWord(){
		return letters.toString();
	}

	
	
	/**
	 * returns the number of letters
	 * @return
	 */
	public int letterCount(){
		return letters.size();
	}

	
	
	
	/**
	 * sums the score achieved by recently placed letters, including multipliers
	 * @return
	 */
	public int calcScore(){
		int currentWordScore;
		int currentWordMultiplier;
		int letterScore;
		int scoreSum = 0;
		//collect all words
		List<TreeSet<GridPoint>> allWords = collectProducedWords();
		//walk each word and sum up the points
		for (TreeSet<GridPoint> aWord : allWords){
			currentWordScore = 0;			//next word
			currentWordMultiplier = 0;
			for (GridPoint p: aWord){
				letterScore = board.getPiece(p).getPoints();
				if (letters.contains(p)){	//letter put in this move
					currentWordScore += letterScore * board.getDetails(p).getLetterMultiplier();
					currentWordMultiplier *= (board.getDetails(p).getWordMultiplier());
				}else{	//letter put in previous moves
					currentWordScore += letterScore;
				}
			}
			//sum up the word
			scoreSum += currentWordScore * ((currentWordMultiplier==0) ? 1 : currentWordMultiplier);

			//50 bonus points for putting all 7 letters (wikipedia/scrabble)
			if (letters.size() == IScrabbleEngine.MAX_PIECES){
				scoreSum += 50;
			}
		}
		return scoreSum;
	}


}
